package com.hns.its.persistence.jpa;

import com.hns.its.common.utils.AppUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.util.CollectionUtils;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.*;
import java.util.Map.Entry;

@NoRepositoryBean
public class GenericRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements GenericRepository<T, ID>, Serializable {

	private static final long serialVersionUID = 1L;

	private static final Logger logger = LoggerFactory.getLogger(GenericRepositoryImpl.class);

	private final JpaEntityInformation<T, ?> entityInformation;
	private final EntityManager em;

	private Class<?> springDataRepositoryInterface;

	public Class<?> getSpringDataRepositoryInterface() {
		return springDataRepositoryInterface;
	}

	public void setSpringDataRepositoryInterface(Class<?> springDataRepositoryInterface) {
		this.springDataRepositoryInterface = springDataRepositoryInterface;
	}

	/**
	 * Creates a new {@link SimpleJpaRepository} to manage objects of the given
	 * {@link JpaEntityInformation}.
	 * 
	 * @param entityInformation
	 * @param entityManager
	 */
	public GenericRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager,
                                 Class<?> springDataRepositoryInterface) {
		super(entityInformation, entityManager);
		this.entityInformation = entityInformation;
		this.em = entityManager;
		this.springDataRepositoryInterface = springDataRepositoryInterface;
	}

	/**
	 * Creates a new {@link SimpleJpaRepository} to manage objects of the given
	 * domain type.
	 * 
	 * @param domainClass
	 * @param em
	 */
	public GenericRepositoryImpl(Class<T> domainClass, EntityManager em) {
		this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em, null);
	}

	protected Class<T> getDomainClass() {
		return entityInformation.getJavaType();
	}

	@Override
	public T findSingleByProp(String propName, Object propValue) {
		String entityName = entityInformation.getEntityName();
		String sql = MessageFormat.format("From {0} where {1} = :value", entityName, propName);
		TypedQuery<T> q = em.createQuery(sql, getDomainClass());
		q.setParameter("value", propValue);
		return q.getSingleResult();
	}

	/**
	 * find entities who's propName value is propValue TODO, 支持多属性
	 * 
	 * @param propName
	 * @param propValue
	 * @return
	 */
	@Override
	public List<T> findByProp(String propName, Object propValue) {
		String entityName = entityInformation.getEntityName();
		String sql = MessageFormat.format("From {0} where {1} = :value", entityName, propName);
		TypedQuery<T> q = em.createQuery(sql, getDomainClass());
		q.setParameter("value", propValue);
		return q.getResultList();
	}

	/**
	 * check whether property TODO, 支持多属性
	 * 
	 * @param name
	 * @param value
	 * @return
	 */
	@Override
	public boolean exists(String propName, Object propValue) {
		return !CollectionUtils.isEmpty(this.findByProp(propName, propValue));
	}

	/*
	 * jpql page
	 * 
	 */
	@Override
	public Page<T> findAll(String jql, Pageable pageable) {
		TypedQuery<T> query = em.createQuery(jql, getDomainClass());
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());
		String countJql = getCountSql(jql);

		TypedQuery<Long> countQuery = em.createQuery(countJql, Long.class);
		// Long total = QueryUtils.executeCountQuery(countQuery);
		Long total = Long.parseLong(countQuery.getSingleResult().toString());

		List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T>emptyList();
		return new PageImpl<T>(content, pageable, total);
	}

	/*
	 * (non-Javadoc)
	 * 当前接口实现类
	 * @see org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<T> findAllBySql(String sql, Pageable pageable) {
		Query query = em.createNativeQuery(sql, getDomainClass());
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());

		// TODO QueryUtils.createCountQueryFor has bug indeed
		String countSql = getCountSql(sql);
		Query countQuery = em.createNativeQuery(countSql);
		Long total = Long.parseLong(countQuery.getSingleResult().toString());
		
		List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T>emptyList();
		return new PageImpl<T>(content, pageable, total);
	}
	
	/**
	 * jpql single
	 */
	@Override
	public T findSingleByProps(Map<String, Object> props) {
		String entityName = entityInformation.getEntityName();
		StringBuffer sql = new StringBuffer("From ").append(entityName);
		if (!props.isEmpty()) {
			sql.append(" where  1=1 ");
			for (Entry<String, Object> entry : props.entrySet()) {
				sql.append(" and ").append(entry.getKey()).append(" = :").append(entry.getKey());
			}
		}

		TypedQuery<T> q = em.createQuery(sql.toString(), getDomainClass());
		for (Entry<String, Object> entry : props.entrySet()) {
			q.setParameter(entry.getKey(), entry.getValue());
		}

		q.setMaxResults(1);
		List list = q.getResultList();

		if (list != null && list.size() > 0) {
			return (T) list.get(0);
		}

		return null;
	}

	/**
	 * jpql lsit
	 */
	@Override
	public List<T> findByProps(Map<String, Object> props) {
		String entityName = entityInformation.getEntityName();
		StringBuffer sql = new StringBuffer("From ").append(entityName);
		if (!props.isEmpty()) {
			sql.append(" where 1=1 ");
			for (Entry<String, Object> entry : props.entrySet()) {
				sql.append(" and ").append(entry.getKey()).append(" = :").append(StringUtils.remove(entry.getKey(), "."));
			}
		}

		TypedQuery<T> q = em.createQuery(sql.toString(), getDomainClass());
		for (Entry<String, Object> entry : props.entrySet()) {
			q.setParameter(StringUtils.remove(entry.getKey(), "."), entry.getValue());
		}
		return q.getResultList();
	}
	
	/**
	 * sql page 自定义类
	 */
	@Override
	public <T> Page<T> findAllBySql(String sql,Collection<SearchFilter> filters, Pageable pageable,Class cls) {
	    sql = this.dynamicSql(sql,filters);
	    
	    String countSql = getCountSql(sql);
        Query countQuery = em.createNativeQuery(countSql);
        this.dynamicQuery(countQuery, filters);
        Long total = Long.parseLong(countQuery.getSingleResult().toString());
	    
        String selectSql = dynamicSort(sql, pageable);
		Query query = em.createNativeQuery(selectSql);
		this.dynamicQuery(query, filters);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());
		query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(cls));  
		List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T>emptyList();
		return new PageImpl<T>(content, pageable, total);
	}
	
	/**
	 * sql list 自定义类
	 */
	@Override
	public <T> List<T> findAllBySql(String sql,Collection<SearchFilter> filters, Class cls) {
	    sql = this.dynamicSql(sql,filters);
	    
		Query query = em.createNativeQuery(sql);
		this.dynamicQuery(query, filters);
		query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(cls));  
		List<T> content =query.getResultList() ;
		return content;
	}
	
	/**
	 * sql single 自定义类
	 */
	@Override
	public <T> T findSingleBySql(String sql, Collection<SearchFilter> filters, Class cls) {
	    sql = this.dynamicSql(sql,filters);
	    
		Query query = em.createNativeQuery(sql);
		this.dynamicQuery(query, filters);
		query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(cls));  
		return (T) query.getResultList().get(0);
	}
	
	
	/**
	 * sql page map
	 */
	@SuppressWarnings({ "deprecation", "unchecked", "rawtypes" })
	@Override
	public Page<Map<String,Object>> findAllBySqlMap(String sql, Collection<SearchFilter> filters, Pageable pageable) {
	    sql = this.dynamicSql(sql,filters);
	    
	    String countSql = getCountSql(sql);
        Query countQuery = em.createNativeQuery(countSql);
        this.dynamicQuery(countQuery, filters);
        Long total = Long.parseLong(countQuery.getSingleResult().toString());
        
        String selectSql = dynamicSort(sql, pageable);
		Query query = em.createNativeQuery(selectSql);
		this.dynamicQuery(query, filters);
		query.setFirstResult(pageable.getOffset());
		query.setMaxResults(pageable.getPageSize());
		query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);  
		List<Map<String,Object>> content = total > pageable.getOffset() ? query.getResultList() : Collections.<Map<String,Object>>emptyList();
		return new PageImpl(content, pageable, total);
	}
	
	/**
	 * sql list map
	 */
	@SuppressWarnings({"deprecation", "unchecked" })
	@Override
	public List<Map<String,Object>> findAllBySqlMap(String sql, Collection<SearchFilter> filters) {
	    sql = this.dynamicSql(sql,filters);
	    
		Query query = em.createNativeQuery(sql);
		this.dynamicQuery(query, filters);
		query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);  
		List<Map<String,Object>> content = query.getResultList();
		return content;
	}
	
	/**
	 * sql Single Map
	 */
	@SuppressWarnings({"deprecation", "unchecked" })
    @Override
	public Map<String,Object> findSingleBySqlMap(String sql, Collection<SearchFilter> filters) {
	    sql = this.dynamicSql(sql,filters);
	    
		Query query = em.createNativeQuery(sql);
		this.dynamicQuery(query, filters);
		query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);  
		return (Map<String,Object>) query.getResultList().get(0);
	}
	
	/**
     * sql page 自定义类 可变参数
     */
	@Override
    public <T> Page<T> findAllBySql(String sql, Pageable pageable, Class cls, Object... params) {
	    String countSql = getCountSql(sql);
        Query countQuery = em.createNativeQuery(countSql);
        dynamicQuery(countQuery, params);
        Long total = Long.parseLong(countQuery.getSingleResult().toString());
        
        String selectSql = dynamicSort(sql, pageable);
        Query query = em.createNativeQuery(selectSql);
        dynamicQuery(query, params);
        query.setFirstResult(pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(cls));  
        List<T> content = total > pageable.getOffset() ? query.getResultList() : Collections.<T>emptyList();
        return new PageImpl<T>(content, pageable, total);
    }

	/**
     * sql list 自定义类 可变参数
     */
    @Override
    public <T> List<T> findAllBySql(String sql, Class cls, Object... params) {
        Query query = em.createNativeQuery(sql);
        this.dynamicQuery(query, params);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(cls));  
        List<T> content =query.getResultList() ;
        return content;
    }
    
    /**
     * sql list 自定义类 可变参数 排序
     */
    @Override
    public <T> List<T> findAllBySqlSort(String sql,Pageable pageable,  Class cls,Object... params) {
        sql = dynamicSort(sql, pageable);
        Query query = em.createNativeQuery(sql);
        this.dynamicQuery(query, params);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(cls));  
        List<T> content =query.getResultList() ;
        return content;
    }

    /**
     * sql single 自定义类 可变参数
     */
    @Override
    public <T> T findSingleBySql(String sql, Class cls, Object... params) {
        Query query = em.createNativeQuery(sql);
        this.dynamicQuery(query, params);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(cls));

        List list = query.getResultList();

        if (list != null && list.size() > 0) {
            return (T) list.get(0);
        }

        return null;
    }

    /**
     * sql page map 可变参数
     */
    @Override
    public Page<Map<String, Object>> findAllBySqlMap(String sql, Pageable pageable, Object... params) {
        String countSql = getCountSql(sql);
        Query countQuery = em.createNativeQuery(countSql);
        this.dynamicQuery(countQuery, params);
        Long total = Long.parseLong(countQuery.getSingleResult().toString());
        
        String selectSql = dynamicSort(sql, pageable);
        Query query = em.createNativeQuery(selectSql);
        this.dynamicQuery(query, params);
        query.setFirstResult(pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);  
        List<Map<String,Object>> content = total > pageable.getOffset() ? query.getResultList() : Collections.<Map<String,Object>>emptyList();
        return new PageImpl(content, pageable, total);
    }

    /**
     * sql list map 可变参数
     */
    @Override
    public List<Map<String, Object>> findAllBySqlMap(String sql, Object... params) {
        Query query = em.createNativeQuery(sql);
        this.dynamicQuery(query, params);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);  
        List<Map<String,Object>> content =query.getResultList() ;
        return content;
    }
    
    /**
     * sql list map 可变参数  (可排序)
     */
    @Override
    public List<Map<String, Object>> findAllBySqlMapSort(String sql, Pageable pageable,Object... params) {
        sql = dynamicSort(sql, pageable);
        Query query = em.createNativeQuery(sql);
        this.dynamicQuery(query, params);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);  
        List<Map<String,Object>> content =query.getResultList() ;
        return content;
    }

    /**
     * sql single map 可变参数
     */
    @Override
    public Map<String, Object> findSingleBySqlMap(String sql, Object... params) {
        Query query = em.createNativeQuery(sql);
        this.dynamicQuery(query, params);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);  
        List<Map<String,Object>> list = query.getResultList();
        Map<String,Object> map = null;
        if(!CollectionUtils.isEmpty(list)){
            map = list.get(0);
        }
        
        return map;
    }

    /**
     * sql list 
     */
    @SuppressWarnings("rawtypes")
    @Override
    public List findAllBySql(String sql) {
        Query query = em.createNativeQuery(sql);
        List content = query.getResultList();
        return content;
    }

	/**
	 * 获取记录数
	 *
	 * @param sql
	 * @param params
	 * @return
	 */
	public Integer countSql(String sql, Object... params) {
		Query countQuery = em.createNativeQuery(sql);
		dynamicQuery(countQuery, params);
		Integer count = Integer.valueOf(countQuery.getSingleResult().toString());
		return count;
	}
	
	/**
	 * @param sql
	 * @return
	 */
	private String getCountSql(String sql) {
		/*String countSql = QueryUtils.createCountQueryFor(sql);
		String countTarget = StringUtils.substringBetween(countSql, "count(", ")");
		countSql = StringUtils.replaceOnce(countSql, countTarget, "*");*/
	    // 子查询里面的不能存在相同的列名 a.id,b.id
        StringBuffer countsql = new StringBuffer(200);
        countsql.append("SELECT COUNT(1) FROM ( ").append(sql).append(" ) counttb ");
		return countsql.toString();
	}

	/**
	 * 
	 * @Description 组装sql
	 * @param selectsql
	 * @param filters
	 * @return
	 */
	public String dynamicSql(String selectsql, Collection<SearchFilter> filters){
	    StringBuffer sql = new StringBuffer(selectsql);
	    if(null != filters && filters.size() > 0){
    	    for (SearchFilter filter : filters) {
                switch (filter.operator) {
                case NN:
                    sql.append(" and ").append(filter.fieldName).append(" is not null ");
                    break;
                case ISN:
                    sql.append(" and ").append(filter.fieldName).append(" is null ");
                    break;
                case NE:
                    sql.append(" and ").append(filter.fieldName).append(" != :").append(filter.fieldName);
                    break;
                case EQ:
                    sql.append(" and ").append(filter.fieldName).append(" = :").append(filter.fieldName);
                    break;
                case LIKE: 
                    sql.append(" and ").append(filter.fieldName).append(" like concat('%',:").append(filter.fieldName).append(",'%')");
                    break;
                case GT:
                    sql.append(" and ").append(filter.fieldName).append(" > :").append(filter.fieldName);
                    break;
                case LT:
                    sql.append(" and ").append(filter.fieldName).append(" < :").append(filter.fieldName);
                    break;
                case GTE:
                    sql.append(" and ").append(filter.fieldName).append(" >= :").append(filter.fieldName);
                    break;
                case LTE:
                    sql.append(" and ").append(filter.fieldName).append(" <= :").append(filter.fieldName);
                    break;
                case IN:
                    sql.append(" and ").append(filter.fieldName).append(" in (:").append(filter.fieldName).append(")");
                    break;
                /*case ORDER:
                    sql.append(" order by ").append(filter.fieldName).append(" ").append(filter.value);
                    break;   */
                default:
                    break;
                }
            }
	    }
	    return sql.toString();
	}
	
	/**
	 * 组装排序
	 * @param selectsql
	 * @param pageable
	 * @return
	 * String
	 */
	public String dynamicSort(String selectsql, Pageable pageable){
	    StringBuffer sql = new StringBuffer(selectsql);
	    Sort sort = pageable.getSort();
        if (AppUtils.isNotEmpty(sort)) {
            sql.append(" ORDER BY ");
            Iterator<Order> iterator = sort.iterator();
            while (iterator.hasNext()) {
                Order order = (Order) iterator.next();
                String orderName = StringUtils.replaceFirst(order.getProperty(), "_", ".");
                sql.append(" "+orderName+" "+order.getDirection().name()+",");
            }
            sql = new StringBuffer(sql.substring(0, sql.length()-1));
        }
	    return sql.toString();
	}
	
	/**
	 * 
	 * @Title:  dynamicQuery
	 * @Description: 设置条件 参数名
	 * @author zhangl
	 * @param query
	 * @param filters
	 * void
	 */
	public void dynamicQuery(Query query,Collection<SearchFilter> filters){
	    if(null != filters && filters.size() > 0){
    	    for (SearchFilter filter : filters) {
    	        if(!filter.operator.name().equals("ORDER")){
    	            query.setParameter(filter.fieldName, filter.value);
    	        }
    	    }
	    }
	} 
	
	/**
	 * 
	 * @Title:  dynamicQuery
	 * @Description: 设置条件 序号
	 * @author zhangl
	 * @param query
	 * @param obj
	 * void
	 */
    @SuppressWarnings("unchecked")
    private void dynamicQuery(Query query, Object... obj) {
        if ((obj != null) && (obj.length > 0)) {
            if (obj[0] instanceof Map) {
                Map<String, Object> params = (Map<String, Object>) obj[0];
                Set<String> keys = params.keySet();
                for (String key : keys) {
                    if (AppUtils.isNotEmpty(params.get(key))) {
                        query.setParameter(key, params.get(key));
                    }
                }
            } else {
                for (int i = 0; i < obj.length; i++) {
                    if (obj[i] != null && AppUtils.isNotEmpty(obj[i].toString())) {
                        query.setParameter(i + 1, obj[i]);
                    }
                }
            }
        }
    }

    @Override
    public void executeUpdate(String sql, Object... params) {
        Query query = em.createNativeQuery(sql);
        this.dynamicQuery(query, params);
        int i = query.executeUpdate();
    }
	

}